using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime.Remoting;
using System.Windows.Forms;
using CppWrapper.Interfaces;
using Microsoft.Win32;

namespace WrapperGenerator
{
  public partial class GeneratorUI : Form
  {
    private const string ABORT = "Abort";
    private const string GENERATE = "Generate";

    private const string READY = "ready";
    private const string WORKING = "working";
    private const string ABORTING = "aborting";

    private const string PROCESSING = "{0}% processed";

    private const string RK_ASSEBMLY = "LastUsedAssembly";
    private const string RK_DIRECTORY = "LastUsedOutDir";
    private const string RK_SELECTED = "CheckedTypes";
    private const string RK_POSTCOMMAND = "PostGenerationCmd";
    private const string RK_RECENT_ASSEMBLIES = "RecentUsedAssemblies";

    private readonly SettingsUI settings;
    private string m_strLastParsedAssembly;

    public GeneratorUI()
    {
      InitializeComponent();

      settings = new SettingsUI();

      LoadSettings();
    }

    private void LoadSettings()
    {
      try
      {
        RegistryKey registry = Application.UserAppDataRegistry;

        RegistryKey recent = registry.OpenSubKey(RK_RECENT_ASSEMBLIES);
        if(null != recent && recent.ValueCount > 0)
        {
          List<string> lstRecent = new List<string>(recent.GetValueNames());
          recent.Close();
          lstRecent.Sort();
          foreach (string strAssembly in lstRecent)
          {
            assemblyPathComboBox.Items.Add(strAssembly);
          }
        }

        object lastAssembly = registry.GetValue(RK_ASSEBMLY, null);
        if (null != lastAssembly)
        {
          RegistryKey types = registry.OpenSubKey(RK_SELECTED, false);
          IList<string> lstSelected = null;
          if (null != types && types.ValueCount > 0)
          {
            lstSelected = new List<String>(types.GetValueNames());
            types.Close();
          }

          OnAssemblyFileChanged(lastAssembly.ToString(), lstSelected);
        }

        object lastOutDir = registry.GetValue(RK_DIRECTORY, null);
        if (null != lastOutDir)
          TargetDirectory = lastOutDir.ToString();

        object postCommand = registry.GetValue(RK_POSTCOMMAND, null);
        if (null != postCommand)
          settings.PostCommand = postCommand.ToString();

        registry.Close();
      }
      catch
      {
        //just to avoid settings-stuff is somehow breaking down the whole app 
      }      
    }

    private void SaveSettings()
    {
      try
      {
        RegistryKey registry = Application.UserAppDataRegistry;

        if (!string.IsNullOrEmpty(assemblyPathComboBox.Text))
          registry.SetValue(RK_ASSEBMLY, assemblyPathComboBox.Text, RegistryValueKind.String);
        if (!string.IsNullOrEmpty(TargetDirectory))
          registry.SetValue(RK_DIRECTORY, TargetDirectory, RegistryValueKind.String);

        RegistryKey recent = registry.OpenSubKey(RK_RECENT_ASSEMBLIES, true);
        if(null == recent)
          recent = registry.CreateSubKey(RK_RECENT_ASSEMBLIES);
        if (null != recent)
        {
          foreach (object oAssemblyName in assemblyPathComboBox.Items)
          {
            if (null != oAssemblyName && null == recent.GetValue(oAssemblyName.ToString(), null))
            {
              recent.SetValue(oAssemblyName.ToString(), 1, RegistryValueKind.DWord);              
            }       
          }
          recent.Flush();
          recent.Close();          
        }        

        registry.SetValue(RK_POSTCOMMAND, settings.PostCommand, RegistryValueKind.String);

        registry.DeleteSubKey(RK_SELECTED, false);
        RegistryKey types = registry.CreateSubKey(RK_SELECTED);
        if(null != types)
        {
          foreach (string strSelected in GetSelectedItems())
            types.SetValue(strSelected, 1, RegistryValueKind.DWord);

          types.Flush();
          types.Close();
        }
        
        registry.Flush();
        registry.Close();
      }
      catch
      {
      	 //just to avoid settings-stuff is somehow breaking down the whole app
      }      
    }

    private int AddRecentAssemblyFile(string strAssemblyFilePath)
    {
      if (string.IsNullOrEmpty(strAssemblyFilePath) || !File.Exists(strAssemblyFilePath))
        return -1;

      int nRecentIndex = assemblyPathComboBox.Items.IndexOf(strAssemblyFilePath);
      if (nRecentIndex < 0)
        nRecentIndex = assemblyPathComboBox.Items.Add(strAssemblyFilePath); 

      return nRecentIndex;
    }

    private IList<string> GetSelectedItems()
    {
      IList<string> lstSelected = new List<string>();
      foreach (TreeNode parentnode in typesTreeView.Nodes)
      {
        if (parentnode.Checked)
          lstSelected.Add(parentnode.Text);

        foreach (TreeNode node in parentnode.Nodes)
        {
          if (node.Checked)
            lstSelected.Add(string.Format("{0}.{1}", parentnode.Text, node.Text));
        }
      }
      return lstSelected;
    }

    private void OnBrowseAssemblies(object sender, EventArgs e)
    {
      OpenFileDialog dialog = new OpenFileDialog();
      if (dialog.ShowDialog(this) == DialogResult.OK)
      {
        OnAssemblyFileChanged(dialog.FileName);
      }
    }

    private void RefreshTypeTreeView(string filePath)
    {
      RefreshTypeTreeView(filePath, null);
    }

    private void RefreshTypeTreeView(string strAssemblyFilePath, IList<string> lstToSelect)
    {
      if (string.IsNullOrEmpty(strAssemblyFilePath) || !File.Exists(strAssemblyFilePath))
        return;

      if (null == lstToSelect)
        lstToSelect = GetSelectedItems();    

      AppDomain seperateAppDomain;
      IGenerator generator = CreateGeneratorInIsolatedDomain(out seperateAppDomain);
      IList<ITypeInfo> typeList = generator.GetTypesFromAssembly(strAssemblyFilePath);
      FillTreeWithTypeInfo(typeList, lstToSelect);
      AppDomain.Unload(seperateAppDomain);

      m_strLastParsedAssembly = strAssemblyFilePath;
    }

    private void OnAssemblyFileChanged(string filePath)
    {
      OnAssemblyFileChanged(filePath, null);
    }

    private void OnAssemblyFileChanged(string filePath, IList<string> lstToSelect)
    {
      int nToSelect = AddRecentAssemblyFile(filePath);
      if (nToSelect >= 0)
      {
        assemblyPathComboBox.BackColor = typesTreeView.BackColor;        

        if (filePath != m_strLastParsedAssembly)
          RefreshTypeTreeView(filePath, lstToSelect);

        if (nToSelect != assemblyPathComboBox.SelectedIndex)
          assemblyPathComboBox.SelectedIndex = nToSelect;
      }
      else
      {
        assemblyPathComboBox.BackColor = System.Drawing.Color.LightCoral;
      }     
    }

    private IGenerator CreateGeneratorInIsolatedDomain(out AppDomain isolatedDomain)
    {
      AppDomain.CurrentDomain.AssemblyLoad += CurrentDomain_AssemblyLoad;
      isolatedDomain  = AppDomain.CreateDomain("SandBox");
      //Type generatorType = typeof(Generator);
      ObjectHandle handle = isolatedDomain.CreateInstance("CppGeneratorService, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null", "CppWrapper.GeneratorService.Generator", false,
                                                       BindingFlags.Public | BindingFlags.Instance, null,
                                                       new object[] { TargetDirectory, resolveReferencesCheckBox.Checked },
                                                       CultureInfo.InvariantCulture, null, null);
      //ObjRef _ref = handle.CreateObjRef(typeof (IGenerator));
      return (IGenerator) handle.Unwrap();
    }

    static void CurrentDomain_AssemblyLoad(object sender, AssemblyLoadEventArgs args)
    {
    }

    private void FillTreeWithTypeInfo(IEnumerable<ITypeInfo> typeList)
    {
      FillTreeWithTypeInfo(typeList, null);
    }

    private void FillTreeWithTypeInfo(IEnumerable<ITypeInfo> typeList, ICollection<string> lstToSelect)
    {
      try
      {
        typesTreeView.BeginUpdate();
        typesTreeView.Nodes.Clear();
        foreach (ITypeInfo info in typeList)
        {
          TreeNode parentNode = typesTreeView.Nodes[info.Namespace];
          if (parentNode==null)
          {
            parentNode = typesTreeView.Nodes.Add(info.Namespace, info.Namespace);
            if(lstToSelect != null && lstToSelect.Contains(info.Namespace))
              parentNode.Checked = true;
          }
          TreeNode node = parentNode.Nodes.Add(info.Name);
          if(lstToSelect != null && lstToSelect.Contains(string.Format("{0}.{1}", info.Namespace, info.Name)))
              node.Checked = true;
          node.Tag = info;
        }
      }
      finally
      {
        typesTreeView.EndUpdate();
      }
    }

    private void OnGenerateButtonClick(object sender, EventArgs e)
    {
      if (generateButton.Text == ABORT)
      {
        generateButton.Enabled = false;
        lblStatus.Text = ABORTING;
        generateWorker.CancelAsync();
      }
      else
      {
        progress.Value = 0;
        progress.ToolTipText = string.Format(PROCESSING, 0);
        lblProgress.Text = string.Format(PROCESSING, 0);
        generateButton.Text = ABORT;
        lblStatus.Text = WORKING;
        generateWorker.RunWorkerAsync();
      }
    }

    private static void FillActivatedNodes(List<ITypeInfo> activatedNodes, TreeNodeCollection nodes)
    {
      foreach (TreeNode node in nodes)
      {
        if (node.Checked && node.Tag is ITypeInfo)
        {
          activatedNodes.Add((ITypeInfo) node.Tag);
        }
        FillActivatedNodes(activatedNodes, node.Nodes);
      }
    }

    private void OnGacButtonClick(object sender, EventArgs e)
    {
      GACBrowser gacBrowser = new GACBrowser();
      if (gacBrowser.ShowDialog(this) == DialogResult.OK)
      {
        AppDomain seperateAppDomain;
        IGenerator generator = CreateGeneratorInIsolatedDomain(out seperateAppDomain);
        try
        {
          IList<ITypeInfo> typeList = generator.GetTypesFromAssembly(gacBrowser.Assembly);
          FillTreeWithTypeInfo(typeList);
        }
        finally
        {
          AppDomain.Unload(seperateAppDomain);
        }
      }
    }

    private void OnTypesTreeViewAfterCheck(object sender, TreeViewEventArgs e)
    {
      foreach (TreeNode treeNode in e.Node.Nodes)
      {
        treeNode.Checked = e.Node.Checked;
      }
    }

    private void OnAssemblyPathTextBoxLeave(object sender, EventArgs e)
    {
      OnAssemblyFileChanged(assemblyPathComboBox.Text);
    }

    private void OnTargetDirChanged(object sender, EventArgs e)
    {
      if (string.IsNullOrEmpty(TargetDirectory) || !Directory.Exists(TargetDirectory))
        outputDirTextBox.BackColor = System.Drawing.Color.LightCoral;
      else
        outputDirTextBox.BackColor = typesTreeView.BackColor;
    }

    private void OnBrowseTargetDir(object sender, EventArgs e)
    {
      FolderBrowserDialog openFileDialog = new FolderBrowserDialog();
      if (!string.IsNullOrEmpty(TargetDirectory))
      {
        openFileDialog.SelectedPath = TargetDirectory;        
      }
      if (openFileDialog.ShowDialog(this)==DialogResult.OK)
      {
        TargetDirectory = openFileDialog.SelectedPath;        
      }
    }

    private string TargetDirectory
    {
      get { return outputDirTextBox.Text; }
      set
      { 
        outputDirTextBox.Text = value;
        OnTargetDirChanged(this, null);
      }
    }

    private void OnSelectAllCheckBoxCheckedChanged(object sender, EventArgs e)
    {
      foreach (TreeNode node in typesTreeView.Nodes)
      {
        node.Checked = ((CheckBox) sender).Checked;
      }
    }

    private void OnRefreshButtonClick(object sender, EventArgs e)
    {
      RefreshTypeTreeView(assemblyPathComboBox.Text);
    }

    private void OnGeneratorDoWork(object sender, DoWorkEventArgs e)
    {
      List<ITypeInfo> activatedNodes = new List<ITypeInfo>();
      FillActivatedNodes(activatedNodes, typesTreeView.Nodes);
      generateWorker.ReportProgress(0);
      AppDomain seperateAppDomain;

      if(chkCleanOutDir.Checked)
      {
        DirectoryInfo outDir = new DirectoryInfo(TargetDirectory);
        if(outDir.Exists)
        {
          foreach (FileInfo file in outDir.GetFiles())
            file.Delete();
        }
      }

      IGenerator generator = CreateGeneratorInIsolatedDomain(out seperateAppDomain);
      generator.Progress += generateWorker.ReportProgress;
      
      try
      {
        generator.Generate(activatedNodes, 
                            delegate { return generateWorker.CancellationPending; }
                          );
      }
      finally
      {
        e.Cancel = generateWorker.CancellationPending;
        AppDomain.Unload(seperateAppDomain);
      }
    }

    private void OnGeneratorCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
      generateButton.Text = GENERATE;
      generateButton.Enabled = true;
      lblStatus.Text = READY;

      ExecuteCommand(settings.PostCommand);
    }

    private static void ExecuteCommand(string strCmd)
    {
      if (string.IsNullOrEmpty(strCmd))
        return;

      try
      {
        int nArgumentsOffset = strCmd.IndexOf(' ');
        if (nArgumentsOffset > 0)
        {
          System.Diagnostics.Process.Start(strCmd.Substring(0, nArgumentsOffset).Trim(), strCmd.Substring(nArgumentsOffset).Trim());
        }
        else
        {
          System.Diagnostics.Process.Start(strCmd.Trim());
        }    
      }
      catch(Exception ex)
      {
        Console.WriteLine(string.Format("Could not execute post-generate-command: {0}", ex.Message));
      }
    }

    private void OnGeneratorProgressChanged(object sender, ProgressChangedEventArgs e)
    {
      progress.Value = e.ProgressPercentage;
      progress.ToolTipText = string.Format(PROCESSING, e.ProgressPercentage);
      lblProgress.Text = string.Format(PROCESSING, e.ProgressPercentage);
    }

    private void OnFormClosing(object sender, FormClosingEventArgs e)
    {
      SaveSettings();
    }

    private void OnMenuSettingsClick(object sender, EventArgs e)
    {
      settings.ShowDialog(this);
    }

    private void OnMenuCloseClick(object sender, EventArgs e)
    {
      Close();
    }
  }
}